﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using ControleRepresentante.Logic;
using ControleRepresentante.Logic.Models;
using ControleRepresentante.Logic.Repositories;
using ControleRepresentante.Logic.Repositories.SearchExtensions;
using ControleRepresentante.Logic.Statistics;
using WebSite.Controllers.ControllerExtensions;
using WebSite.ViewModels.DetailsViewModels;
using WebSite.ViewModels.EditViewModels;
using WebSite.ViewModels.IndexViewModels;
using WebSite.ViewModels.ViewModelExtensions.DetailsViewModelExtensions;
using WebSite.ViewModels.ViewModelExtensions.EditViewModelExtensions;
using WebSite.ViewModels.ViewModelExtensions.IndexViewModelExtensions;

namespace WebSite.Controllers
{
    public abstract class CRUDControllerBase<TRepository, TModel, TStatistics> : ControllerBase<TRepository>
        where TRepository : class, IRepository<TModel, TStatistics>
        where TModel : ModelBase 
        where TStatistics : IStatistics
    {

        #region Data Members

        private readonly ISessionConfiguration m_SessionConfiguration;
        private IIndexColumnsProvider m_IndexColumnsProvider;

        #endregion

        #region Constructors

        protected CRUDControllerBase(IRepositoryProvider repositoryProvider, ISessionConfiguration sessionConfiguration, IControllerExtensionProvider controllerExtensionProvider, params object[] repositoryParams)
            : base(repositoryProvider, controllerExtensionProvider, repositoryParams)
        {
            m_SessionConfiguration = sessionConfiguration;
        }

        #endregion

        #region Protected Properties

        protected TModel Model
        {
            get { return m_SessionConfiguration.GetValue<TModel>(this, "Model"); }
            set { m_SessionConfiguration.SetValue(this, "Model", value); }
        }

        #endregion

        #region Abstract Functions

        protected virtual bool CanDelete(TModel model, out string message)
        {
            message = null;
            return true;
        }

        protected abstract IIndexViewModel GetIndexViewModel(TStatistics statistics);
        protected abstract EditViewModel GetEditViewModel(TModel model, bool isNew);
        protected abstract DetailsViewModel GetDetailsViewModel(TModel model);

        #endregion

        #region Actions

        protected ActionResult RedirectToAction(string actionName, Guid modelId)
        {
            return RedirectToAction(actionName, new { id = modelId });
        }

        public ActionResult Index()
        {
            EnsureHasPermission(Operacao.View);
            Model = null;
            TStatistics statistics;
            IEnumerable<TModel> models = Repository.GetModels(out statistics, GetPage());
            return Index(models, statistics);
        }

        protected int GetPage()
        {
            if (Request == null) return 1;
            string pageValue = Request["page"];
            if (!String.IsNullOrEmpty(pageValue)) return Convert.ToInt32(pageValue);
            string key = Request.QueryString.AllKeys.FirstOrDefault(x => x.EndsWith("-page"));
            return key == null ? 1 : Convert.ToInt32(Request.QueryString[key]);
        }

        private IIndexViewModelExtension[] GetIndexViewModelExtensions(IControllerExtension[] controllerExtensions, FormCollection formCollection)
        {
            return controllerExtensions.Select(x => x.GetIndexViewModelExtension(formCollection, Repository)).Where(x => x != null).ToArray();
        }

        private IEditViewModelExtension[] GetEditViewModelExtensions()
        {
            return GetControllerExtensions().Select(x => x.EditViewModelExtension).Where(x => x != null).ToArray();
        }

        private IDetailsViewModelExtension[] GetDetailsViewModelExtensions()
        {
            return GetControllerExtensions().Select(x => x.DetailsViewModelExtension).Where(x => x != null).ToArray();
        }

        protected ISearchExtension[] GetSearchExtensions(FormCollection formCollection)
        {
            return GetControllerExtensions().Select(x => x.GetSearchExtension(formCollection)).Where(x => x != null).ToArray();
        }

        protected ActionResult Index(IEnumerable<TModel> models, TStatistics statistics, FormCollection formCollection = null, Action<IIndexViewModel> action = null)
        {
            return GetIndexActionResult("Index", statistics, models, formCollection, action);
        }

        protected ActionResult PrintList(IEnumerable<TModel> models, TStatistics statistics, FormCollection formCollection)
        {
            return GetIndexActionResult("Print", statistics, models, formCollection);
        }

        private ActionResult GetIndexActionResult(string viewName, TStatistics statistics, IEnumerable<TModel> models, FormCollection formCollection, Action<IIndexViewModel> action = null)
        {
            Model = null;

            IIndexViewModel indexViewModel = GetIndexViewModel(statistics);
            IControllerExtension[] controllerExtensions = GetControllerExtensions();
            IIndexViewModelExtension[] indexViewModelExtensions = GetIndexViewModelExtensions(controllerExtensions, formCollection);

            indexViewModel.IndexColumnsProvider = GetIndexColumnsProvider();
            indexViewModel.ControllerName = ControllerName;
            indexViewModel.Models = models;
            indexViewModel.IndexViewModelExtensions = indexViewModelExtensions;
            indexViewModel.CanCreate = HasPermission(Operacao.Create);
            indexViewModel.CanEdit = HasPermission(Operacao.Update);
            indexViewModel.CanDelete = HasPermission(Operacao.Delete);
            indexViewModel.CanPrint = CanPrint;
            indexViewModel.ModelCount = statistics.ModelCount;
            indexViewModel.PageSize = Repository.PageSize;
            Representante representante = Repository.GetRepresentante();
            if (representante != null)
            {
                indexViewModel.RepresentanteInfo = representante.RepresentanteInfo;
            }

            if (action != null)
            {
                action(indexViewModel);
            }

            foreach (TModel model in models)
            {
                foreach (IControllerExtension controllerExtension in controllerExtensions)
                {
                    controllerExtension.SetModel(model);
                }
            }
            return View(viewName, indexViewModel);
        }

        protected virtual bool CanPrint { get { return false; } }

        internal IIndexColumnsProvider GetIndexColumnsProvider()
        {
            if (m_IndexColumnsProvider == null)
            {
                m_IndexColumnsProvider = new IndexColumnsProvider(Area, Repository.User);
                RegisterIndexColumns(m_IndexColumnsProvider);
            }
            return m_IndexColumnsProvider;
        }

        protected virtual void RegisterIndexColumns(IIndexColumnsProvider indexColumnsProvider)
        {
            IControllerExtension[] controllerExtensions = GetControllerExtensions();
            foreach (IControllerExtension controllerExtension in controllerExtensions)
            {
                controllerExtension.RegisterIndexColumns(indexColumnsProvider);
            }
        }

        private string ControllerName { get { return GetType().Name.Replace("Controller", String.Empty); } }

        public ActionResult Create()
        {
            return Create(true);
        }

        protected virtual ActionResult Create(bool clearModel)
        {
            EnsureHasPermission(Operacao.Create);

            if (clearModel || Model == null)
            {
                Model = CreateInstance();
            }

            EditViewModel editViewModel = GetEditViewModel(Model, true);
            editViewModel.Model = Model;
            editViewModel.ControllerName = ControllerName;
            editViewModel.EditViewModelExtensions = GetEditViewModelExtensions();
            return View("Create", editViewModel);
        }

        protected virtual TModel CreateInstance()
        {
            TModel model = (TModel)Activator.CreateInstance(typeof(TModel));
            foreach (IControllerExtension controllerExtension in GetControllerExtensions())
            {
                controllerExtension.SetModel(model);
            }
            return model;
        }

        public virtual ViewResult Details(Guid id)
        {
            return View(GetDetailsViewModel(id));
        }

        protected DetailsViewModel GetDetailsViewModel(Guid id)
        {
            EnsureHasPermission(Operacao.View);
            Model = Repository.GetModelById(id);
            foreach (IControllerExtension controllerExtension in GetControllerExtensions())
            {
                controllerExtension.SetModel(Model);
            }
            DetailsViewModel detailsViewModel = GetDetailsViewModel(Model);
            detailsViewModel.Model = Model;
            detailsViewModel.ControllerName = ControllerName;
            detailsViewModel.DetailsViewModelExtensions = GetDetailsViewModelExtensions();
            detailsViewModel.CanDelete = HasPermission(Operacao.Delete);
            detailsViewModel.CanEdit = HasPermission(Operacao.Update);
            return detailsViewModel;
        }

        public ActionResult Edit(Guid id)
        {
            return Edit((Guid?)id);
        }

        protected virtual ActionResult Edit(Guid? id = null)
        {
            EnsureHasPermission(Operacao.Update);
            if (id.HasValue)
            {
                Model = Repository.GetModelById(id.Value);
                foreach (IControllerExtension controllerExtension in GetControllerExtensions())
                {
                    controllerExtension.SetModel(Model);
                }
            }
            return Edit("Edit");
        }

        protected ActionResult Edit(string viewName, bool isNew = false)
        {
            EditViewModel editViewModel = GetEditViewModel(Model, isNew);
            editViewModel.Model = Model;
            editViewModel.ControllerName = ControllerName;
            editViewModel.EditViewModelExtensions = GetEditViewModelExtensions();
            return View(viewName, editViewModel);
        }

        public ActionResult Delete(Guid id)
        {
            EnsureHasPermission(Operacao.Delete);
            Model = Repository.GetModelById(id);
            foreach (IControllerExtension controllerExtension in GetControllerExtensions())
            {
                controllerExtension.SetModel(Model);
            }
            DetailsViewModel detailsViewModel = GetDetailsViewModel(Model);
            detailsViewModel.Model = Model;
            detailsViewModel.ControllerName = ControllerName;
            detailsViewModel.DetailsViewModelExtensions = GetDetailsViewModelExtensions();
            string message;
            if (!CanDelete(Model, out message))
            {
                detailsViewModel.Message = message;
            }
            return View(detailsViewModel);
        }

        [HttpPost]
        public virtual ActionResult Delete(Guid id, FormCollection collection)
        {
            EnsureHasPermission(Operacao.Delete);
            Repository.RemoveModelById(id);
            Model = null;
            return RedirectToAction("Index");
        }

        protected void ExtensionsUpdateModel(FormCollection formCollection)
        {
            foreach (IControllerExtension controllerExtension in GetControllerExtensions())
            {
                Dictionary<string, string> errors = controllerExtension.TryUpdateModel(Model, formCollection);
                foreach (KeyValuePair<string, string> error in errors)
                {
                    ModelState.AddModelError(error.Key, error.Value);
                }
            }
        }

        [HttpPost]
        public ActionResult SetFields(FormCollection formCollection)
        {
            User user = Repository.User;

            if (user == null)
            {
                throw new RestrictedAreaException();
            }

            List<Guid> builtInIndexColumns = new List<Guid>();
            List<Guid> attributeIndexColumns = new List<Guid>();

            foreach (string key in formCollection.AllKeys.Where(x => !x.StartsWith(IndexViewModel.HiddenKey)))
            {
                if (key.StartsWith(IndexViewModel.AttributeKey))
                {
                    Guid id;
                    if (Guid.TryParse(key.Substring(IndexViewModel.AttributeKey.Length), out id) &&
                        formCollection[key].ToLower() != "false")
                    {
                        attributeIndexColumns.Add(id);
                    }
                }
                else
                {
                    Guid id;
                    if (Guid.TryParse(key, out id) && formCollection[key].ToLower() != "false")
                    {
                        builtInIndexColumns.Add(id);
                    }
                }
            }

            IEnumerable<ControleRepresentante.Logic.Models.Attribute> attribures;
            if (attributeIndexColumns.Any())
            {
                IAttributeRepository attributeRepository = RepositoryProvider.GetRepository<IAttributeRepository>();
                attribures = attributeRepository.GetModelsByIds(attributeIndexColumns);
            }
            else
            {
                attribures = new ControleRepresentante.Logic.Models.Attribute[0];
            }
            user.SetIndexColumnsPreferences(Area.Value, builtInIndexColumns, attribures);
            RepositoryProvider.GetRepository<IUserRepository>().UpdateModel(user);
            return Index();
        }

        #endregion
    }
}